#ifndef EVICTION_CACHE_EVICTION_HPP
#define EVICTION_CACHE_EVICTION_HPP

#include "utils/cxx-include.hpp"

#include "core/exception.hpp"
#include "core/log.hpp"

/**
 * \file
 * \ingroup cacheEvict
 * \brief cas::CacheEviction declaration.
 */

/**
 * \defgroup cacheEvict Cache Eviction
 * \brief Cache eviction algorithm.
 */

namespace cas
{
    /**
     * \ingroup cacheEvict
     */
    struct CacheEvictionStatistic
    {
        uint64_t requestNumber{0};     /**< total request number */
        uint64_t requestSize{0};       /**< total request size of LBA */
        uint64_t predictedDataSize{0}; /**< total size from prefetch */
        uint64_t wastedDataSize{0};    /**< total size of eviction */

        uint64_t requestHitNumber{0};
        uint64_t requestHitSize{0};

        uint64_t effectivePrefetchSize{0}; /**< total hit prefetched data */
        uint64_t uselessPrefetchSize{0};   /**< total evicted prefetched data */

        double getHitRatio() const;
        double getPrefetchPrecision() const;
    };

    /**
     * \ingroup cacheEvict
     * How to locate a unique block
     */
    struct CachePage
    {
        uint16_t devno;    /**< device no. */
        uint64_t lba : 40; /**< logical blocks address */

        CachePage(uint16_t devno = 0u, uint64_t lba = 0u) : devno(devno), lba(lba){};

        friend inline bool operator==(const CachePage &lhs, const CachePage &rhs)
        {
            return lhs.devno == rhs.devno && lhs.lba == rhs.lba;
        }
        friend inline bool operator<(const CachePage &lhs, const CachePage &rhs)
        {
            return lhs.devno < rhs.devno || lhs.lba < rhs.lba;
        }
    } __attribute__((packed));

    /**
     * \ingroup cacheEvict
     * a batch of requests
     */
    struct CacheRequest
    {
        enum CacheRequestType : int8_t
        {
            CacheRequestNone,      /**< no specific function */
            CacheRequestLookup,    /**< lookup */
            CacheRequestUpdate,    /**< refresh a cache item */
            CacheRequestOverride,  /**< mark an item as invalid */
            CacheRequestPrefetch,  /**< update by prefetch algorithm */
            CacheRequestTypeLength /**< CacheRequestType length */
        };

        CachePage page;        /**< starting logical blocks */
        uint16_t size;         /**< number of logical blocks in sequence */
        CacheRequestType type; /**< type of request */

        CacheRequest(CacheRequestType _type, uint16_t _devno = 0, uint64_t _lba = 0, uint16_t _size = 1);
    } __attribute__((packed));

    /**
     * \ingroup cacheEvict
     */
    class CacheEviction
    {
    public:
        CacheEvictionStatistic stat; /**< module statistic */

        CacheEviction();
        virtual ~CacheEviction();

        /**
         * \param [in] request cache request for lookup
         * \return number of hit blocks, \c 0 for cache miss
         */
        virtual uint16_t lookup(const CacheRequest &request);

        /**
         * \brief reset cache
         * \return \c true if reset success
         */
        virtual void reset();

        /**
         * \brief Set cache size and reset cache
         * \param [in] cacheSize new value
         */
        virtual void setCacheSize(uint32_t cacheSize);

        /**
         * \brief Get total cache size
         * \return cache size
         */
        virtual uint32_t getCacheSize() const;

        /**
         * \brief Get cache hit ratio
         * \return hit ratio, 0-1
         */
        virtual double getHitRatio() const;
    };
} // namespace cas

namespace std
{
    /**
     * \ingroup cacheEvict
     * \brief CAS CachePage hash function specialization
     */
    template <>
    struct hash<cas::CachePage>
    {
        // static const std::hash<std::uint64_t> hash_fn;
        size_t operator()(const cas::CachePage &s) const
        {
            const uint64_t h = ((uint64_t)s.devno << 48) ^ ((uint64_t)s.lba);
            // return hash<cas::CachePage>::hash_fn(h);
            return std::hash<std::uint64_t>{}(h);
        }
    };
} // namespace std

#endif /* EVICTION_CACHE_EVICTION_HPP */